package com.authine.cloudpivot.ext.service.impl;

import java.util.LinkedHashMap;
import java.util.Map;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.authine.cloudpivot.ext.domain.SystemSetting;
import com.authine.cloudpivot.ext.mapper.SystemSettingMapper;
import com.authine.cloudpivot.ext.service.ThirdPartyAppService;
import com.authine.cloudpivot.ext.util.DdConfigSign;
import com.authine.cloudpivot.web.api.dubbo.DubboConfigService;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.DingTalkSignatureUtil;
import com.dingtalk.api.request.OapiGetJsapiTicketRequest;
import com.dingtalk.api.request.OapiServiceGetCorpTokenRequest;
import com.dingtalk.api.request.OapiUserGetuserinfoRequest;
import com.dingtalk.api.response.OapiGetJsapiTicketResponse;
import com.dingtalk.api.response.OapiServiceGetCorpTokenResponse;
import com.dingtalk.api.response.OapiUserGetuserinfoResponse;
import com.dingtalk.oapi.lib.aes.DingTalkEncryptor;

import lombok.extern.slf4j.Slf4j;

/**
 * @author
 */
@Service
@Slf4j
public class ThirdPartyAppServiceImpl implements ThirdPartyAppService {

    @SuppressWarnings("unused")
    @Autowired
    private DubboConfigService dubboConfigService;

    /*@Value("${third-party-app.token}")
    private String token;
    
    @Value("${third-party-app.encodingAesKey}")
    private String encodingAesKey;
    
    @Value("${third-party-app.suiteKey}")
    private String suiteKey;
    
    @Value("${third-party-app.suiteSecret}")
    private String suiteSecret;
    */
    @Value("${project.sso.auth-url}")
    private String authUrl;

    /**
     * 创建应用，验证回调URL创建有效事件（第一次保存回调URL之前）
     */
    private static final String EVENT_CHECK_CREATE_SUITE_URL = "check_create_suite_url";

    /**
     * 创建应用，验证回调URL变更有效事件（第一次保存回调URL之后）
     */
    private static final String EVENT_CHECK_UPADTE_SUITE_URL = "check_update_suite_url";

    /**
     * suite_ticket推送事件
     */
    private static final String EVENT_SUITE_TICKET = "suite_ticket";

    /**
     * 企业授权开通应用事件
     */
    private static final String EVENT_TMP_AUTH_CODE = "tmp_auth_code";

    private static final RestTemplate restTemplate = new RestTemplate();

    @Resource
    private SystemSettingMapper systemSettingMapper;

    @Override
    public Object dingCallback(String signature, Long timestamp, String nonce, JSONObject body, String token,
        String encodingAesKey, String suiteKey, String suiteSecret) {
        log.info("推送suiteTicket开始 >>>");

        String params = "signature:" + signature + " timestamp:" + timestamp + " nonce:" + nonce + " body:" + body;
        log.info(params);
        try {
            log.info("begin callback:" + params);
            DingTalkEncryptor dingTalkEncryptor = new DingTalkEncryptor(token, encodingAesKey, suiteKey);
            // 从post请求的body中获取回调信息的加密数据进行解密处理
            String encrypt = body.getString("encrypt");
            String plainText = dingTalkEncryptor.getDecryptMsg(signature, timestamp.toString(), nonce, encrypt);
            JSONObject callBackContent = JSON.parseObject(plainText);
            JSONArray bizDataArray = callBackContent.getJSONArray("bizData");
            if (bizDataArray != null && bizDataArray.size() > 0) {
                for (Object obj : bizDataArray) {
                    JSONObject bizData = (JSONObject)obj;
                    String suiteId = bizData.getString("biz_id");
                    String bizType = bizData.getString("biz_type");
                    if ("2".equals(bizType)) {
                        String suiteTicket = bizData.getJSONObject("biz_data").getString("suiteTicket");
                        log.info("bizId={},suiteTicket={}", suiteId, suiteTicket);
                        insertSuiteTicket(suiteId, suiteKey + "|" + suiteSecret, suiteTicket);
                        break;
                    }
                }
            }
            // 根据回调事件类型做不同的业务处理
            String eventType = callBackContent.getString("EventType");
            if (EVENT_CHECK_CREATE_SUITE_URL.equals(eventType)) {
                log.info("验证新创建的回调URL有效性: " + plainText);
            } else if (EVENT_CHECK_UPADTE_SUITE_URL.equals(eventType)) {
                log.info("验证更新回调URL有效性: " + plainText);
            } else if (EVENT_SUITE_TICKET.equals(eventType)) {
                // suite_ticket用于用签名形式生成accessToken(访问钉钉服务端的凭证)，需要保存到应用的db。
                // 钉钉会定期向本callback url推送suite_ticket新值用以提升安全性。
                // 应用在获取到新的时值时，保存db成功后，返回给钉钉success加密串（如本demo的return）
                log.info("应用suite_ticket数据推送: " + plainText);
            } else if (EVENT_TMP_AUTH_CODE.equals(eventType)) {
                // 本事件应用应该异步进行授权开通企业的初始化，目的是尽最大努力快速返回给钉钉服务端。用以提升企业管理员开通应用体验
                // 即使本接口没有收到数据或者收到事件后处理初始化失败都可以后续再用户试用应用时从前端获取到corpId并拉取授权企业信息，进而初始化开通及企业。
                log.info("企业授权开通应用事件: " + plainText);
            } else {
                // 其他类型事件处理
                log.info("其他类型事件: " + plainText);
            }
            // 返回success的加密信息表示回调处理成功
            Map<String, String> resultMap = dingTalkEncryptor.getEncryptedMap("success", timestamp, nonce);
            StringBuilder msg = new StringBuilder("返回给钉钉的数据:");
            for (Map.Entry<String, String> entry : resultMap.entrySet()) {
                msg.append(entry.getKey()).append("=").append(entry.getValue()).append(";");
            }
            log.info(msg.toString());
            log.info("<<< 推送suiteTicket结束");
            return resultMap;
        } catch (Exception e) {
            // 失败的情况，应用的开发者应该通过告警感知，并干预修复
            log.error("process callback fail." + params, e);
            return "fail：" + e.getMessage();
        }
    }

    @Override
    public JSONObject login(String authCode, String corpId, String suiteId) throws Exception {
        log.info("authCode={},corpId={},suiteId={}", authCode, corpId, suiteId);
        // 获取accessToken,注意正是代码要有异常流处理
        OapiServiceGetCorpTokenResponse oapiServiceGetCorpTokenResponse = getOapiServiceGetCorpToken(corpId, suiteId);
        String accessToken = oapiServiceGetCorpTokenResponse.getAccessToken();
        String userId = getUserId(accessToken, authCode);
        log.debug("========== userId: {} ==========", userId);

        // BizObjectQueryModel bizObjectQueryModel = new BizObjectQueryModel();
        // bizObjectQueryModel.setQueryCode("town_village_Corpid");
        // bizObjectQueryModel.setSchemaCode("town_village_Corpid");
        // bizObjectQueryModel.setFilterExpr(Q.it("village_corpid", FilterExpression.Op.In, corpId));
        // Page<BizObjectModel> bizObjectModelPage =
        // dubboConfigService.getBizObjectFacade().queryBizObjects(bizObjectQueryModel);
        // String townCorpIdStr = "";
        // if (bizObjectModelPage.getTotal() > 0) {
        // Object townCorpId = bizObjectModelPage.getContent().get(0).getData().get("town_corpid");
        // townCorpIdStr = null != townCorpId ? townCorpId.toString() : corpId;
        // } else {
        // townCorpIdStr = corpId;
        // }

        String url = authUrl.replaceAll("oauth", "") + "/api/login/sso?code=" + userId + "," + corpId;
        // String url = authUrl.replaceAll("oauth", "") + "/api/login/sso?code=" + userId;
        JSONObject result = restTemplate.getForObject(url, JSONObject.class);
        log.debug("result={}", result);
        return result;
    }

    /**
     * 获取access_token
     * 
     * @param corpId
     * @param suiteId
     * @return
     * @throws Exception
     */
    private OapiServiceGetCorpTokenResponse getOapiServiceGetCorpToken(String corpId, String suiteId) throws Exception {
        long timestamp = System.currentTimeMillis();
        // 正式应用应该由钉钉通过开发者的回调地址动态获取到
        SystemSetting systemSetting = getSuiteTicket(suiteId);
        String suiteTicket = systemSetting.getParamValue();
        String id = systemSetting.getId();
        String[] split = id.split("\\|");
        String suiteKey = split[0];
        String suiteSecret = split[1];
        log.debug("suiteKey={},suiteTicket={},suiteSecret={}", suiteKey, suiteTicket, suiteSecret);
        String signature = DingTalkSignatureUtil.computeSignature(suiteSecret,
            DingTalkSignatureUtil.getCanonicalStringForIsv(timestamp, suiteTicket));
        Map<String, String> params = new LinkedHashMap<>();
        params.put("timestamp", String.valueOf(timestamp));
        params.put("suiteTicket", suiteTicket);
        params.put("accessKey", suiteKey);
        params.put("signature", signature);
        String queryString = DingTalkSignatureUtil.paramToQueryString(params, "utf-8");
        DingTalkClient client =
            new DefaultDingTalkClient("https://oapi.dingtalk.com/service/get_corp_token?" + queryString);
        OapiServiceGetCorpTokenRequest request = new OapiServiceGetCorpTokenRequest();
        request.setAuthCorpid(corpId);
        OapiServiceGetCorpTokenResponse response;
        response = client.execute(request);
        if (response == null) {
            log.error("response is null");
        } else if (!response.isSuccess()) {
            log.error("errorCode={},errorMsg={}", response.getErrcode(), response.getMessage());
        }
        return response;
    }

    private String getUserId(String accessToken, String code) {
        log.info("getUserId start >>>");
        log.info("accessToken={},code={}", accessToken, code);
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/getuserinfo");
        OapiUserGetuserinfoRequest request = new OapiUserGetuserinfoRequest();
        request.setCode(code);
        request.setHttpMethod("GET");
        log.debug("accessToken={},code={}", accessToken, code);
        OapiUserGetuserinfoResponse response;
        String userId = "";
        try {
            response = client.execute(request, accessToken);
            if (response == null) {
                log.error("response is null");
            } else {
                if (!response.isSuccess()) {
                    log.error("errorCode={},errorMsg={}", response.getErrcode(), response.getMessage());
                } else {
                    String body = response.getBody();
                    log.debug(body);
                    userId = JSONObject.parseObject(body).getJSONObject("result").getString("userid");
                }
            }
        } catch (Exception e) {
            log.error(e.toString());
        }
        log.info("<<< getUserId end");
        return userId;
    }

    @Transactional(rollbackFor = Exception.class)
    public void insertSuiteTicket(String suiteId, String suiteKeySecret, String suiteTicket) {
        systemSettingMapper.insertSuiteTicket(suiteId, suiteKeySecret, suiteTicket);
    }

    /**
     * suiteTicket是一个定时变化的票据，主要目的是为了开发者的应用与钉钉之间访问时的安全加固。 测试应用：可随意设置，钉钉只做签名不做安全加固限制。
     * 正式应用：开发者应该从自己的db中读取suiteTicket,suiteTicket是由开发者在开发者平台设置的应用回调地址，由钉钉定时推送给应用，
     * 由开发者在回调地址所在代码解密和验证签名完成后获取到的.正式应用钉钉会在开发者代码访问时做严格检查。
     *
     * @return suiteTicket
     */
    public SystemSetting getSuiteTicket(String suiteId) {
        // 正式应用必须由应用回调地址从钉钉推送获取
        return systemSettingMapper.selectSuiteTicket(suiteId);
    }

    /**
     * 获取JSAPI授权
     * 
     * @param url
     * @param corpId
     * @param suiteId
     * @return
     * @throws Exception
     */
    @Override
    public JSONObject getJspaiAuthentication(String url, String corpId, String suiteId) throws Exception {
        log.info("getJspaiAuthentication start >>>");
        log.debug("url={},corpId={},suiteId={}", url, corpId, suiteId);
        OapiServiceGetCorpTokenResponse oapiServiceGetCorpToken = getOapiServiceGetCorpToken(corpId, suiteId);
        String accessToken = oapiServiceGetCorpToken.getAccessToken();
        log.debug("accessToken={}", accessToken);
        if (accessToken == null) {
            log.error(oapiServiceGetCorpToken.getMessage());
            return null;
        }
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/get_jsapi_ticket");
        OapiGetJsapiTicketRequest req = new OapiGetJsapiTicketRequest();
        req.setHttpMethod("GET");
        OapiGetJsapiTicketResponse rsp = client.execute(req, accessToken);
        String body = rsp.getBody();
        log.debug(body);
        JSONObject jsonObject = JSONObject.parseObject(body);
        long timeStamp = System.currentTimeMillis();
        String nonceStr = "cloudpivot";
        String signature = DdConfigSign.sign(jsonObject.getString("ticket"), nonceStr, timeStamp, url);
        JSONObject result = new JSONObject();
        result.put("timeStamp", timeStamp);
        result.put("nonceStr", nonceStr);
        result.put("signature", signature);
        log.debug("getJspaiAuthentication-result={}", result);
        log.info("<<< getJspaiAuthentication end");
        return result;
    }
}
